// Importing new photos into Mission
// Copyright 2006 (C) Ralph Thomas

#include <apps/photo/import.h>
#include <apps/photo/photodb.h>
#include <adobe/name.hpp>
#include <boost/filesystem/operations.hpp>
#include <boost/filesystem/fstream.hpp>
#include <boost/bind.hpp>
#include <util/db.h>
#include <util/md5.h>
#include <vector>
#include <sstream>
#include <iostream>
#include <libexif/exif-data.h>
#include <libexif/exif-utils.h>
#include <libexif/exif-loader.h>
#include <libexif/exif-tag.h>
#include <jpeglib.h>
#include <jpeg/transupp.h>
extern "C" {
#include <jpegtools.h>
#include <genthumbnail.h>
}
#include <string.h>

#define THUMB_MAX	65536

namespace bfs = boost::filesystem;

namespace apps {
	namespace photo {
		static void single_result( std::string& val, const adobe::name_t& name, const adobe::dictionary_t& dict ) {
			val = dict.get<std::string>( name );
		}
		bool import_image( const bfs::path& p, const bfs::path& target, eImportType how, const std::string& tag ) {
			if( !bfs::exists( p ) || !bfs::exists( target ) ) return false;
			bfs::path path( p ); path.normalize();
			//
			// Take an MD5 sum of the first 10k of this JPEG (hopefully EXIF) image,
			// we use it to determine if we already have this image in our library.
			//
			bfs::filebuf bbuf;
			std::streambuf* sbuf = bbuf.open( path, std::ios_base::in | std::ios_base::binary );
			if( !sbuf ) return false;
			std::string sum( util::md5( sbuf, 10240 ) );
			//
			// Does this sum already exist in the database?
			//
			util::db_ptr_t db( util::db::fromPath( getPhotoDatabasePath() ) );
			std::string sum_count;
			db->query( "select count(*) as num from photos where sum = '" + sum + "';", boost::bind( &single_result, boost::ref( sum_count ), adobe::static_name_t( "num" ), _1 ) );
			//
			// This photo is a duplicate.
			//
			if( sum_count != "0" ) return false;
			//
			// Specify the full path and ensure it's unique by taking the id from
			// the database.
			//
			std::string serial_number;
			db->query( "select count(*) as num from photos;", boost::bind( &single_result, boost::ref( serial_number ), adobe::static_name_t( "num" ), _1 ) );
			//
			// Make the target filename (for moving or copying).
			// 
			bfs::path target_file( target );
			target_file /= bfs::path( serial_number + ".jpg", bfs::native );
			target_file.normalize();
			if( how == kMove || how == kCopy ) {
				//
				// If this file already exists then erase it, so
				// long as it's not also the source copy, in which
				// case do nothing.
				//
				if( target_file != path && bfs::exists( target_file ) )
					bfs::remove( target_file );
				//
				// Now we either have to move or copy the file.
				// XXX: if the source and dest are on the same
				// XXX: filesystem and we're doing a move then
				// XXX: we could just do a rename. I don't know
				// XXX: how to check that, though.
				//
				try {
					bfs::copy_file( path, target_file );
					if( how == kMove ) bfs::remove( path );
				} catch( std::exception& ex ) {
					//
					// XXX: What's the right thing to show
					// XXX: the user here?
					//
					std::cerr << "Error while importing file: " << path.native_file_string() << " : " << ex.what() << std::endl;
				}
			} else target_file = path;
			//
			// Verify that we have an image where we should.
			//
			if( !bfs::exists( target_file ) ) return false;
			//
			// Extract the EXIF date from the image. This is
			// the best one to use for sorting.
			//
			std::string import_time( "datetime('now')" );
			ExifTag exif_tag( exif_tag_from_name( "DateTime" ) );
			ExifData* ed( exif_data_new_from_file( target_file.native_file_string().c_str() ) );
			if( ed ) {
				char buf[1024];
				//
				// Search for the datetime entry in the
				// various EXIF IFDs until we find it.
				//
				for( int i = 0; i < EXIF_IFD_COUNT; i++ ) {
					ExifEntry* entry( exif_content_get_entry( ed->ifd[i], exif_tag ) );
					if( !entry ) continue;
					import_time = std::string( "'" ) + exif_entry_get_value( entry, buf, sizeof( buf ) ) + "'";
					break;
				}
				exif_data_unref( ed );
			}
			//
			// Attempt to re-orient the image according to it's
			// EXIF orientation tag.
			//
			char* thumbnail( new char [THUMB_MAX] );
			char* filename( strdup( target_file.native_file_string().c_str() ) );
			int flags( JFLAG_TRANSFORM_IMAGE | JFLAG_TRANSFORM_THUMBNAIL | JFLAG_UPDATE_ORIENTATION );
			int tsize = create_thumbnail( filename, (unsigned char*) thumbnail, THUMB_MAX );
			jpeg_transform_inplace( filename, (JXFORM_CODE) -1, NULL, thumbnail, tsize, flags );
			delete[] thumbnail;
			free( filename );
			//
			// Create a preview of the image, before we update
			// the database (which will cause the app to examine
			// the file).
			//
			//toolkit::createPreview( "image/jpeg", target_file );
			//
			// Now insert an entry into the database for this new
			// image.
			//
			long long int row = db->execute( "insert into photos (filename, rating, exif_date, import_date, sum) values ( 'file://" + escape_db( target_file.native_file_string() ) + "', 0, " + import_time + ", datetime('now'), '" + sum + "' );" );
			//
			// Look-up the tag in the tag database. If it doesn't exist
			// then we have to create it.
			//
			std::string tag_id = add_tag( tag );
			//
			// Add an entry to the photo_tag_map.
			//
			std::stringstream ss;
			ss << "insert into photo_tag_map (photo_id, tag_id) values (" << row << "," << tag_id << ");";
			db->execute( ss.str() );
			return true;
		}
		typedef std::vector<bfs::path> path_list_t;
		void import_directory( const bfs::path& path, const bfs::path& target, eImportType how, const std::string& tag, const progress_func_t& progress ) {
			if( !bfs::exists( path ) || !bfs::exists( target ) ) return;
			//
			// To get a progress report, we have to build a list of paths
			// before importing them.
			//
			path_list_t path_list;
			bfs::directory_iterator first( path );
			bfs::directory_iterator last;
			while( first != last ) { if( bfs::exists( *first ) ) path_list.push_back( *first ); first++; }
			if( path_list.empty() ) return;
			//
			// Now we can import the images.
			//
			double percentage = 0.0;
			double measure = 100.0 / path_list.size();
			for( path_list_t::iterator i = path_list.begin(); i != path_list.end(); i++ ) {
				std::string url( "file://" + i->native_file_string() );
				//
				// XXX: Verify that this is a JPEG file.
				//
				if( progress ) progress( url, percentage );
				percentage += measure;
				import_image( *i, target, how, tag );
			}
		}
	}
}

